import sets


class Path(object):
    def __init__(self, points=None):
        if None == points:
            self.points = []
        else:
            self.points = points
        
        
class Formation(object):
    def __init__(self, paths):
        self.paths = paths
        

class Entity(object):
    def __init__(self, user_data=None):
        self.user_data = user_data

    def SimulationStep(self, time_delta):
        pass


class Order(object):
    def __init__(self, game_state):
        self.__game_state = game_state
    
    # Subclasses should override this. This method only alters entities'
    # behavior states. It should not directly alter the position, timers, 
    # etc.
    def UpdateEntityBehaviorStates(self):
        raise 'abstract method'
    
    # Subclasses should provide an implementation that causes the order to 
    # ignore the given entity. If the entity is not affected by the order
    # in the first place, this method should do nothing.
    def IgnoreEntity(self, entity):
        raise 'abstract method'
    
    # This should be called by subclasses when the order has been completed by
    # all entities.
    def _CompleteOrder(self):
        self.__game_state.CompleteOrder(self)


class BehaviorStateEntity(Entity):    
    def __init__(self, user_data=None, behavior_state=None):
        super(BehaviorStateEntity, self).__init__(user_data=user_data)
        self._behavior_state = behavior_state
          
    def SimulationStep(self, time_delta):
        self._DoBehavior(time_delta)
        
    # Subclasses should override this with their own behaviors. Behaviors
    # should not alter the behavior state.
    def _DoBehavior(self, time_delta):
        pass

        
class PositionEntity(BehaviorStateEntity):
    def __init__(self, position, user_data=None, behavior_state=None):
        super(PositionEntity, self).__init__(
              user_data=user_data, behavior_state=behavior_state)
        self._position = position
        
    def _GetPosition(self):
        return self._position
    
    # Public position property is read-only
    position = property(_GetPosition)
    
    def _SetPosition(self, position):
        self._position = position
        

class Unit(PositionEntity):
    _DO_NOTHING_STATE = 0
    _MOVEMENT_STATE = 1
    
    def __init__(self, pos, user_data=None):
        super(Unit, self).__init__(pos, user_data=user_data, 
                                   behavior_state=Unit._DO_NOTHING_STATE)
        self._speed = 10
        self._movement_destination = None
        
    def _SetDoNothingState(self):
        self._behavior_state = Unit._DO_NOTHING_STATE
        
    def _SetMovementState(self, destination):
        self._behavior_state = Unit._MOVEMENT_STATE
        self._movement_destination = destination

    def _DoBehavior(self, time_delta):
        if self._behavior_state is Unit._DO_NOTHING_STATE:
            return
        elif self._behavior_state is Unit._MOVEMENT_STATE:
            destination_vector = self._movement_destination - self._position
            destination_distance = destination_vector.length
            if destination_distance <= (self._speed * time_delta):
                self._SetPosition(self._movement_destination)
            else:
                destination_direction = destination_vector.Unit()
                self._SetPosition(self.position + 
                  (self._speed * time_delta * destination_direction))


class UnitSetOrder(Order):
    def __init__(self, game_state, units):
        super(UnitSetOrder, self).__init__(game_state)
        self._units = sets.Set(units)
        
    def IgnoreEntity(self, unit):
        self._units.discard(unit)


class UnitMoveOrder(UnitSetOrder):
    def __init__(self, game_state, units, destination):
        super(UnitMoveOrder, self).__init__(game_state, units)
        self._destination = destination
        self._units_at_destination = sets.Set()
        
    def UpdateEntityBehaviorStates(self):
        for unit in self._units:
            if self._destination == unit.position:
                unit._SetDoNothingState()
                self._units_at_destination.add(unit)
            else:
                unit._SetMovementState(self._destination)
        if len(self._units_at_destination) == len(self._units):
            self._CompleteOrder()
    
    def IgnoreEntity(self, unit):
        super(UnitMoveOrder, self).IgnoreEntity(unit)
        self._units_at_destination.discard(unit)

                  
class GameState(object):
    def __init__(self):
        self._entities = []
        self._add_entity_callback = None
        self._orders = []
        self._paths = []
        self._formations = []
        
    def GetNumEntities(self):
        return len(self._entities)
    
    def SimulationStep(self, time_delta):
        for order in self._orders:
            order.UpdateEntityBehaviorStates()
        for entity in self._entities:
            entity.SimulationStep(time_delta)
                        
    def _AddEntity(self, entity):
        assert isinstance(entity, Entity)
        self._entities.append(entity)
        if self._add_entity_callback:
            self._add_entity_callback(entity)
    
    # TODO(afass): This should just be an abstract method
    def SetAddEntityCallback(self, callback):
        self._add_entity_callback = callback
        
    def AddUnit(self, pos):
        self._AddEntity(Unit(pos))
        
    def AddPath(self, path):
        self._paths.append(path)
        
    def AddFormation(self, formation):
        self._formations.append(formation)
             
    def GetEntitiesInRect(self, world_rect):
        return_set = sets.Set()
        for entity in self._entities:
            if (isinstance(entity, PositionEntity) and 
                world_rect.Contains(entity.position)):
                return_set.add(entity)
        return return_set
    
    def GetPathsInRect(self, world_rect):
        return_set = sets.Set()
        for path in self._paths:
            # TODO(afass): test if path is in rect
            return_set.add(path)
        return return_set
    
    # This should only be called by subclasses of Order when the order
    # has been completed.
    def CompleteOrder(self, order):
        self._orders.remove(order)
    
    # Issues the order and causes all other orders of the same type to ignore
    # units affected by this order.
    def _IssueExclusiveOrder(self, order, entities):
        for existing_order in self._orders:
            for entity in entities:
                existing_order.IgnoreEntity(entity)
        self._orders.append(order)
                
    def IssueUnitMoveOrder(self, units, destination):
        unit_move_order = UnitMoveOrder(self, units, destination)
        self._IssueExclusiveOrder(unit_move_order, units)